﻿using System;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;

namespace common.controls
{
    //Adapted from http://www.codeproject.com/Articles/9391/Dragging-tree-nodes-in-C by Michea77
    public class TreeViewExt : TreeView 
    {
        public TreeViewExt()
        {
            this.AllowDrop = true;
            timer.Interval = 200;
            timer.Tick += new EventHandler(timer_Tick);
        }

        /// <summary>
        /// Copy subtree from one node to another
        /// </summary>
        /// <param name="fromNode"></param>
        /// <param name="toNode"></param>
        public static void Copy(TreeNode fromNode, TreeNode toNode)
        {
            foreach (TreeNode childNode in fromNode.Nodes)
            {
                TreeNode newNode = (TreeNode)childNode.Clone();
                toNode.Nodes.Add(newNode);
            }
        }

        private Keys _myKeyModifierDropToRoot = Keys.Alt;
        /// <summary>
        /// Provide a way to allow user to drag a node to the tree root. When user drags a node to a top-most node, 
        /// the drag-node is dropped to the tree root (if the key is hold) instead of to the selected drop node. 
        /// </summary>
        public Keys myKeyModifierDropToRoot
        {
            get { return _myKeyModifierDropToRoot; }
            set { _myKeyModifierDropToRoot = value; }
        }


        public delegate void OnNodeMoved(object sender,TreeNode node);
        public event OnNodeMoved myOnNodeMoved = null;
        
        // Node being dragged
        private TreeNode dragNode = null;
        ImageList imageListDrag = new ImageList();
        // Timer for scrolling
        private Timer timer = new Timer();
        // Temporary drop node for selection
        private TreeNode tempDropNode = null;

        protected override void OnItemDrag(ItemDragEventArgs e)
        {
            base.OnItemDrag(e);

            // Get drag node and select it
            this.dragNode = (TreeNode)e.Item;
            this.SelectedNode = this.dragNode;

            // Reset image list used for drag image
            this.imageListDrag.Images.Clear();
            //this.imageListDrag.ImageSize = new Size(this.dragNode.Bounds.Size.Width + this.Indent, this.dragNode.Bounds.Height);
            int w = this.dragNode.Bounds.Size.Width + this.Indent;
            int h  = this.dragNode.Bounds.Height;
            this.imageListDrag.ImageSize =  new Size((w>256?256:w), (h>256?256:h));

            // Create new bitmap
            // This bitmap will contain the tree node image to be dragged
            Bitmap bmp = new Bitmap(this.dragNode.Bounds.Width + this.Indent, this.dragNode.Bounds.Height);

            // Get graphics from bitmap
            Graphics gfx = Graphics.FromImage(bmp);

            // Draw node icon into the bitmap
            //gfx.DrawImage(this.imageListTreeView.Images[0], 0, 0);
            if (this.ImageList!=null && this.ImageList.Images.Count > 0) 
                gfx.DrawImage(this.ImageList.Images[0], 0, 0);

            // Draw node label into bitmap
            gfx.DrawString(this.dragNode.Text,
                this.Font,
                new SolidBrush(this.ForeColor),
                (float)this.Indent, 1.0f);

            // Add bitmap to imagelist
            this.imageListDrag.Images.Add(bmp);

            // Get mouse position in client coordinates
            Point p = this.PointToClient(Control.MousePosition);

            // Compute delta between mouse position and node bounds
            int dx = p.X + this.Indent - this.dragNode.Bounds.Left;
            int dy = p.Y - this.dragNode.Bounds.Top;

            // Begin dragging image
            if (DragHelper.ImageList_BeginDrag(this.imageListDrag.Handle, 0, dx, dy))
            {
                // Begin dragging
                this.DoDragDrop(bmp, DragDropEffects.Move);
                // End dragging image
                DragHelper.ImageList_EndDrag();
            }
        }
        protected override void OnDragOver(DragEventArgs e)
        {
            base.OnDragOver(e);

            // Compute drag position and move image
            Point formP = this.PointToClient(new Point(e.X, e.Y));
            DragHelper.ImageList_DragMove(formP.X - this.Left, formP.Y - this.Top);

            // Get actual drop node
            TreeNode dropNode = this.GetNodeAt(this.PointToClient(new Point(e.X, e.Y)));
            if (dropNode == null)
            {
                e.Effect = DragDropEffects.None;
                return;
            }

            e.Effect = DragDropEffects.Move;

            // if mouse is on a new node select it
            if (this.tempDropNode != dropNode)
            {
                DragHelper.ImageList_DragShowNolock(false);
                this.SelectedNode = dropNode;
                DragHelper.ImageList_DragShowNolock(true);
                tempDropNode = dropNode;
            }

            // Avoid that drop node is child of drag node 
            TreeNode tmpNode = dropNode;
            while (tmpNode.Parent != null)
            {
                if (tmpNode.Parent == this.dragNode) e.Effect = DragDropEffects.None;
                tmpNode = tmpNode.Parent;
            }
        }

        protected override void OnDragDrop(DragEventArgs e)
        {
            base.OnDragDrop(e); 
            // Unlock updates
            DragHelper.ImageList_DragLeave(this.Handle);

            // Get drop node
            TreeNode dropNode = this.GetNodeAt(this.PointToClient(new Point(e.X, e.Y)));

            // If drop node isn't equal to drag node, add drag node as child of drop node
            if (this.dragNode != dropNode)
            {
                // Remove drag node from parent
                if (this.dragNode.Parent == null)
                {
                    this.Nodes.Remove(this.dragNode);
                }
                else
                {
                    this.dragNode.Parent.Nodes.Remove(this.dragNode);
                }

                // Add drag node to root if myKeyModifierDropToRoot is hold
                TreeNode completeDragNode = null;
                if (dropNode.Parent == null &&  (Control.ModifierKeys & this.myKeyModifierDropToRoot) != 0) 
                {
                    completeDragNode = this.Nodes[this.Nodes.Add(this.dragNode)];
                }
                else  // Add drag node to drop node
                {
                    completeDragNode = dropNode.Nodes[dropNode.Nodes.Add(this.dragNode)];
                    dropNode.ExpandAll();
                }
                if (myOnNodeMoved != null && completeDragNode!=null)
                {
                    myOnNodeMoved(this, completeDragNode);
                }
                // Set drag node to null
                this.dragNode = null;

                // Disable scroll timer
                this.timer.Enabled = false;
            }
        }
        protected override void OnDragEnter(DragEventArgs e)
        {
            base.OnDragEnter(e); 
            DragHelper.ImageList_DragEnter(this.Handle, e.X - this.Left, e.Y - this.Top);
            // Enable timer for scrolling dragged item
            this.timer.Enabled = true;
        }

        protected override void OnDragLeave(System.EventArgs e)
        {
            base.OnDragLeave(e); 
            DragHelper.ImageList_DragLeave(this.Handle);

            // Disable timer for scrolling dragged item
            this.timer.Enabled = false;
        }
        private void timer_Tick(object sender, EventArgs e)
        {
            try
            {
                // get node at mouse position
                Point pt = PointToClient(Control.MousePosition);
                TreeNode node = this.GetNodeAt(pt);

                if (node == null) return;

                // if mouse is near to the top, scroll up
                if (pt.Y < 30)
                {
                    // set actual node to the upper one
                    if (node.PrevVisibleNode != null)
                    {
                        node = node.PrevVisibleNode;

                        // hide drag image
                        DragHelper.ImageList_DragShowNolock(false);
                        // scroll and refresh
                        node.EnsureVisible();
                        this.Refresh();
                        // show drag image
                        DragHelper.ImageList_DragShowNolock(true);

                    }
                }
                // if mouse is near to the bottom, scroll down
                else if (pt.Y > this.Size.Height - 30)
                {
                    if (node.NextVisibleNode != null)
                    {
                        node = node.NextVisibleNode;

                        DragHelper.ImageList_DragShowNolock(false);
                        node.EnsureVisible();
                        this.Refresh();
                        DragHelper.ImageList_DragShowNolock(true);
                    }
                }
            }
            catch { }
        }
    }
    public class DragHelper
    {
        [DllImport("comctl32.dll")]
        public static extern bool InitCommonControls();

        [DllImport("comctl32.dll", CharSet = CharSet.Auto)]
        public static extern bool ImageList_BeginDrag(IntPtr himlTrack, int
            iTrack, int dxHotspot, int dyHotspot);

        [DllImport("comctl32.dll", CharSet = CharSet.Auto)]
        public static extern bool ImageList_DragMove(int x, int y);

        [DllImport("comctl32.dll", CharSet = CharSet.Auto)]
        public static extern void ImageList_EndDrag();

        [DllImport("comctl32.dll", CharSet = CharSet.Auto)]
        public static extern bool ImageList_DragEnter(IntPtr hwndLock, int x, int y);

        [DllImport("comctl32.dll", CharSet = CharSet.Auto)]
        public static extern bool ImageList_DragLeave(IntPtr hwndLock);

        [DllImport("comctl32.dll", CharSet = CharSet.Auto)]
        public static extern bool ImageList_DragShowNolock(bool fShow);

        static DragHelper()
        {
            InitCommonControls();
        }
    }
}
